#include "jsnix.h"
#include <fcntl.h>


static int _io_read(JSCTX *ctx)
{
	int fd = __get_int_value(ctx, 0);
	int maxlen = __get_int_value(ctx, 1);
	int rc = -1;

	int oid = __start_object(ctx);
	if(maxlen > 0)
	{
		char *val = (char *)malloc(maxlen);
		if(val != NULL){
			rc = read(fd, val, maxlen);
			if(rc > 0){
				duk_push_lstring(ctx, val, rc);
				duk_put_prop_string(ctx, oid, "buf");
			}
			
			free(val);
		}
	}
	
	__put_int_prop(ctx, oid, "length", rc);

	return 1;
}

static int _io_write(JSCTX *ctx){
	int fd = __get_int_value(ctx, 0);
	size_t sz;
	void *buf = duk_get_buffer(ctx, 1, &sz);
	int rc = -1;

	if(sz > 0)
		rc = write(fd, buf, sz);	

	__put_int_value(ctx, rc);

	return 1;
}

static int _io_close(JSCTX *ctx){
	__put_int_value(ctx, close(__get_int_value(ctx, 0)));
	return 1;
}

static int _io_set_nonblock(JSCTX *ctx){
	int fd = __get_int_value(ctx, 0);
	int set = 1;
	if(__count(ctx) > 1)
		set = __get_int_value(ctx, 1);

	int flags;
  	int r;

  	do
  	  r = fcntl(fd, F_GETFL);
  	while (r == -1 && errno == EINTR);

  	if (r == -1)
	{
		perror("set_nonblock::fcntl(F_GETFL) failed");
		return 0;
	}

  	/* Bail out now if already set/clear. */
  	if (!!(r & O_NONBLOCK) == !!set)
  	  	return 0;

  	if (set)
    		flags = r | O_NONBLOCK;
  	else
    		flags = r & ~O_NONBLOCK;

  	do
    		r = fcntl(fd, F_SETFL, flags);
  	while (r == -1 && errno == EINTR);

	__put_int_value(ctx, r);

	return 1;
}

static int _io_sync(JSCTX *ctx)
{
        sync();
        return 0;
}

#ifdef __SYNCFS__
static int _io_syncfs(JSCTX *ctx)
{
        syncfs(__get_int_value(ctx, 0));
        return 0;
}
#endif

static int _io_sendfile(JSCTX *ctx)
{
        int out_fd = __get_int_value(ctx, 0);
        int in_fd = __get_int_value(ctx, 1);
        off_t offset = __get_int_value(ctx, 2);
        size_t count = __get_int_value(ctx, 3);

        __put_int_value(ctx, sendfile(out_fd, in_fd, &offset, count));

        return 1;
}

static int _io_offset_send(JSCTX *ctx)
{
        int out_fd = duk_to_int(ctx, 0);
        int tlen;
        const char *p = duk_to_lstring(ctx, 1, &tlen);
        int offset = duk_to_int(ctx, 2);
        int rc;

        if(offset >= tlen)
                rc = -1;
        else{
                int wlen = tlen - offset;
                rc = write(out_fd, p+offset, wlen);
        }

        duk_push_int(ctx, rc);

        return 1;
}


static int _io_epoll_create(JSCTX *ctx)
{
	int epoll_fd = epoll_create(MAX_JS_POLL_EVENTS);
	duk_push_int(ctx, epoll_fd);
	
	return 1;
}

static int _io_epoll_op(JSCTX *ctx, int op)
{
	int pollfd = __get_int_value(ctx, 0);
	int fd = __get_int_value(ctx, 1);
	const char *events = __get_string_value(ctx, 2);
	struct epoll_event ev;
	int rc;

	ev.data.fd = fd;
	if(strcmp(events, "in") == 0)
		ev.events = EPOLLIN;
	else if(strcmp(events, "out") == 0)
		ev.events = EPOLLOUT;
	else if(strcmp(events, "in&out") == 0)
		ev.events = EPOLLIN | EPOLLOUT;
	else
		ev.events = EPOLLIN;

	rc = epoll_ctl(pollfd, op, fd, &ev);
	__put_int_value(ctx, rc);

	return 1;
}

static int _io_epoll_add(JSCTX *ctx)
{
	return _io_epoll_op(ctx, EPOLL_CTL_ADD);
}

static int _io_epoll_modify(JSCTX *ctx)
{
	return _io_epoll_op(ctx, EPOLL_CTL_MOD);
}
static int _io_epoll_remove(JSCTX *ctx)
{
	int pollfd = __get_int_value(ctx, 0);
	int fd = __get_int_value(ctx, 1);
	struct epoll_event ev;

	int rc = epoll_ctl(pollfd, EPOLL_CTL_DEL, fd, &ev);
	__put_int_value(ctx, rc);

	return 1;
}


static int _io_epoll_wait(JSCTX *ctx)
{	
	struct epoll_event events[MAX_JS_POLL_EVENTS];
	int pollfd = __get_int_value(ctx, 0);
	int timeout = __get_int_value(ctx, 1);

//	printf("yos:epoll_wait: pollfd=%d, timeout=%d\n", pollfd, timeout);
	int rc = epoll_wait(pollfd, events, MAX_JS_POLL_EVENTS, timeout);
       	int oid = __start_object(ctx);
	__put_int_prop(ctx, oid, "rc", rc);
	if(rc > 0){
		int revents = duk_push_array(ctx);
		int i;
		for(i = 0; i<rc; i++)
		{
			int eid = duk_push_object(ctx);
			char *ename = "";
			__put_int_prop(ctx, eid, "fd", events[i].data.fd);
			if( events[i].events&EPOLLIN && events[i].events&EPOLLOUT)
				ename = "in&out";
			else if(events[i].events & EPOLLIN)
				ename = "in";
			else if(events[i].events & EPOLLOUT)
				ename = "out";
			else if(events[i].events & EPOLLHUP)
				ename = "hup";
			else
				ename = "unknown";
			__put_string_prop(ctx, eid, "ev", ename);


			duk_put_prop_index(ctx, revents, i);

		}
		duk_put_prop_string(ctx, oid, "events");
	}
	
	// printf("epoll_wait return %d\n", rc);

	return 1;
}

static  int _io_select(JSCTX *ctx)
{
	fd_set readfds, writefds, exceptfds;
	int maxfd = 0, rfds, wfds, efds;
	int rfds_list[MAX_JS_POLLFDS], wfds_list[MAX_JS_POLLFDS], efds_list[MAX_JS_POLLFDS];
	struct timeval tv = {0,0};
	int i, fd;
	int retval = -1;

	if(!duk_is_object(ctx, 0))
		return 0;

	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);

	duk_to_object(ctx, 0);

	duk_get_prop_string(ctx, 0, "rfds");
	{
		duk_to_object(ctx, -1);
		rfds = duk_get_length(ctx, -1);
		if(rfds> MAX_JS_POLLFDS)
			goto error;
		for(i=0; i<rfds; i++){
			duk_get_prop_index(ctx, -1, i);
			fd = duk_to_int(ctx, -1);
			duk_pop(ctx);
			if(maxfd < fd)
				maxfd = fd;
			FD_SET(fd, &readfds);
			rfds_list[i] = fd;
		}
		//printf("*total rfds=%d\n", rfds);
	}
	duk_pop(ctx);

      	duk_get_prop_string(ctx, 0, "wfds");
        {
                duk_to_object(ctx, -1);
                wfds = duk_get_length(ctx, -1);
                if(wfds> MAX_JS_POLLFDS)
                        goto error;
                for(i=0; i<wfds; i++){
                        duk_get_prop_index(ctx, -1, i);
                        fd = duk_to_int(ctx, -1);
                        duk_pop(ctx);
                        if(maxfd < fd)
                                maxfd = fd;
                        FD_SET(fd, &writefds);
			wfds_list[i] = fd;
                }
                //printf("*total wfds=%d\n", wfds);
        }
        duk_pop(ctx);


      duk_get_prop_string(ctx, 0, "efds");
        {
                duk_to_object(ctx, -1);
                efds = duk_get_length(ctx, -1);
                if(efds> MAX_JS_POLLFDS)
                        goto error;
                for(i=0; i<efds; i++){
                        duk_get_prop_index(ctx, -1, i);
                        fd = duk_to_int(ctx, -1);
                        duk_pop(ctx);
                        if(maxfd < fd)
                                maxfd = fd;
                        FD_SET(fd, &exceptfds);
			efds_list[i] = fd;
                }
               // printf("*total efds=%d\n", efds);
        }
        duk_pop(ctx);

	duk_get_prop_string(ctx, 0, "timeval");
	{
		duk_to_object(ctx, -1);
		
		duk_get_prop_string(ctx, -1, "sec");
		tv.tv_sec = duk_to_int32(ctx, -1);
		duk_pop(ctx);

		duk_get_prop_string(ctx, -1, "usec");
		tv.tv_usec = duk_to_int32(ctx, -1);
		duk_pop(ctx);
	//	printf("*timeval sec=%d, usec=%d\n", tv.tv_sec, tv.tv_usec);
	}

	duk_pop(ctx);
	maxfd ++;

	retval = select(maxfd, &readfds, &writefds, &exceptfds, &tv);
	if(retval == -1)
		goto error;

       	int obj_idx = duk_push_object(ctx);
       	duk_push_int(ctx, retval);
       	duk_put_prop_string(ctx,  obj_idx, "rc");
	if(retval > 0){
		int robj = duk_push_array(ctx);
		int cid = 0;
		for(i=0; i<rfds; i++)
		{
			if(FD_ISSET(rfds_list[i], &readfds))
			{
				duk_push_int(ctx, rfds_list[i]);
				duk_put_prop_index(ctx, robj, cid);
				cid ++;
			}
		}
		duk_put_prop_string(ctx, obj_idx, "rfds");

		int wobj = duk_push_array(ctx);
		cid = 0;
		for(i=0; i<wfds; i++)
		{
			if(FD_ISSET(wfds_list[i], &writefds))
			{
				duk_push_int(ctx, wfds_list[i]);
				duk_put_prop_index(ctx, wobj, cid);
				cid ++;
			}
		}
		duk_put_prop_string(ctx, obj_idx, "wfds");

		int eobj = duk_push_array(ctx);
		cid = 0;
		for(i=0; i<efds; i++)
		{
			if(FD_ISSET(efds_list[i], &exceptfds))
			{
				duk_push_int(ctx, efds_list[i]);
				duk_put_prop_index(ctx, eobj, cid);
				cid ++;
			}
		}
		duk_put_prop_string(ctx, obj_idx, "efds");
	}


	return 1;
error:
//	perror("select failed");
//	return DUK_RET_ERROR;
	__put_int_value(ctx, retval);

	return 1;
}


int load_io_module(JSCTX *ctx){

        __start_global_object(ctx, "io");

	__register_function(ctx, "read", _io_read, 2);
	__register_function(ctx, "write", _io_write, 2);
	__register_function(ctx, "recv", _io_read, 2);
	__register_function(ctx, "send", _io_write, 2);
	__register_function(ctx, "close", _io_close, 1);
	__register_function(ctx, "sync", _io_close, 0);
	__register_function(ctx, "sendfile", _io_sendfile, 4);
	__register_function(ctx, "offset_send", _io_offset_send, 3);
	__register_function(ctx, "select", _io_select, 1);
	__register_function(ctx, "epoll_create", _io_epoll_create, 0);
	__register_function(ctx, "epoll_add", _io_epoll_add, 3);
	__register_function(ctx, "epoll_modify", _io_epoll_modify, 3);
	__register_function(ctx, "epoll_remove", _io_epoll_remove, 3);
	__register_function(ctx, "epoll_wait", _io_epoll_wait, 2);
	__register_function(ctx, "set_nonblock", _io_set_nonblock, __VARARGS);

	__end_object(ctx);

	return 0;
}

